Option Explicit

'################################################################
' Descriptor
' 数组矩阵，需要在初始化时指定各维度的大小，并且不可扩容
' 对外使用时的感觉:
' Dimension([1,2,3]) --> 3
' Dimension([Dimension([1,2,3]), Dimension([1,2,3]), Dimension([1,2,3]), Dimension([1,2,3])]) --> 4,3
' Dimension([
'     Dimension([Dimension([1,2,3]), Dimension([1,2,3]), Dimension([1,2,3]), Dimension([1,2,3])]),
'     Dimension([Dimension([1,2,3]), Dimension([1,2,3]), Dimension([1,2,3]), Dimension([1,2,3])]),
'     Dimension([Dimension([1,2,3]), Dimension([1,2,3]), Dimension([1,2,3]), Dimension([1,2,3])]),
'     Dimension([Dimension([1,2,3]), Dimension([1,2,3]), Dimension([1,2,3]), Dimension([1,2,3])]),
'     Dimension([Dimension([1,2,3]), Dimension([1,2,3]), Dimension([1,2,3]), Dimension([1,2,3])]),
' ]) -- > 5,4,3
'
' 实际实现时的感觉:
' Dimension 按照索引，切入指维度的指定索引位置
' [[1,2,3],[1,2,3],[1,2,3],[1,2,3]]
' [[1,2,3],[1,2,3],[1,2,3],[1,2,3]]
' [[1,2,3],[1,2,3],[1,2,3],[1,2,3]]
' [[1,2,3],[1,2,3],[1,2,3],[1,2,3]]
' [[1,2,3],[1,2,3],[1,2,3],[1,2,3]]

' [[1,2,3],[1,2,3],[1,2,3],[1,2,3]] 0
' [1,2,3] 0,3 -> 1 level
'################################################################

'################################################################
' Field
'################################################################
' 矩阵的维度数量
Private fieldDimensionCount As Integer
' 各维度的长度 int[]
' 从 0 开始
Private fieldSizeArray As Variant

' 数据缓存，整体上是一个多维数组
Private cache As Variant

'################################################################
' Constructor
'################################################################
Private Sub Class_Initialize()
End Sub

Private Sub Class_Terminate()
End Sub

'################################################################
' Init
'################################################################
' 使用可变长度参数来初始化矩阵的维度长度
' @param-array sizeArray 各个维度的长度
' m.Init 2, 3
Public Sub Init(ParamArray sizeArray() As Variant)
    Dim min As Integer
    Dim max As Integer
    min = LBound(sizeArray)
    max = UBound(sizeArray)

    ' 1. 处理 0 维矩阵
    ' 0 维矩阵，允许存在，但是不需要再做任何初始化了，直接退出
    If (max - min + 1) = 0 Then
        Exit Sub
    End if

    ' 2. 遍历所有维度长度，并转换成数字类型
    ReDim fieldSizeArray(0 to (max - min))
    Dim i As Integer
    Dim j As Integer
    j = 0
    For i = min To max
        fieldSizeArray(j) = val(sizeArray(i))
        j = j + 1
    Next

    ' 3. 保存维度数量
    fieldDimensionCount = max - min + 1

    ' 4. 创建缓存数据
    cache = UtilsArray.createMultiDimensionArrayBySizeArray(fieldSizeArray)
End Sub

' 使用 int 数组来初始化矩阵的维度长度
' @param sizeArray 维度数组
' m.InitBySizeArray Arrays(2, 3)
Public Sub InitBySizeArray(ByRef sizeArray As Variant)
    Dim min As Integer
    Dim max As Integer
    min = LBound(sizeArray)
    max = UBound(sizeArray)

    ' 1. 处理 0 维矩阵
    ' 0 维矩阵，允许存在，但是不需要再做任何初始化了，直接退出
    If (max - min + 1) = 0 Then
        Exit Sub
    End if

    ' 2. 遍历所有维度长度，并转换成数字类型
    ReDim fieldSizeArray(0 to (max - min))
    Dim i As Integer
    Dim j As Integer
    j = 0
    For i = min To max
        fieldSizeArray(j) = val(sizeArray(i))
        j = j + 1
    Next

    ' 3. 保存维度数量
    fieldDimensionCount = max - min + 1

    ' 4. 创建缓存数据
    cache = UtilsArray.createMultiDimensionArrayBySizeArray(fieldSizeArray)
End Sub

'################################################################
' Property
'################################################################
' 返回矩阵的维度数量
Public Property Get DimensionCount() As Integer
    DimensionCount = fieldDimensionCount
End Property

' 因为 vba 的数组是值类型，直接返回数组对象会产生复制行为，所以在这里不提供该方法
' 返回矩阵的原始数组
'Public Property Get InnerData() As Variant
'    InnerData = cache
'End Property

'################################################################
' Function
'################################################################
' 获取指定维度级别的长度
' @param levelIndex 维度的索引，从 0 开始
' @return 指定维度的长度
'         如果超过了维度数量，或者小于 0，将会抛出异常
Public Function getDimensionLevelSize(ByRef levelIndex As Integer) As Integer
    ' 1. 检查索引范围
    ' 如果超过了最大维度，或者小于 0，将会返回 -1
    If levelIndex < 0 Then
        Err.Raise 20020, Description:="ArrayMatrix_getDimensionLevelSize: levelIndex[" & levelIndex & "] is less than 0"
        Exit Function
    End If
    If levelIndex >= fieldDimensionCount Then
        Err.Raise 20021, Description:="ArrayMatrix_getDimensionLevelSize: levelIndex[" & levelIndex & "] is greate than max dimension index[" & (fieldDimensionCount - 1) & "]"
        Exit Function
    End If

    ' 2. 获取指定维度的长度
    getDimensionLevelSize = fieldSizeArray(levelIndex)
End Function

' 按照指定的各个维度级别的索引来获取维度对象
' @param nextDimensionIndexes 下一级维度及其以后的索引
' @return 下 X 个维度的 ArrayMatrixDimension 对象
'         对于 0 - N 级的维度，最多返回 N-1 级。第 N 级是真实数据将不会创建维度对象，如果尝试获取将会抛出异常 ！！！！
'         如果维度索引的范围超过了维度的长度或者小于 0，将会抛出异常 ！！！！
'         如果没有指定任何参数，将会抛出异常 ！！！！
Public Function getSubDimensionByParams(ParamArray nextDimensionIndexes() As Variant) As ArrayMatrixDimension
    Dim indexSize As Integer
    indexSize = UBound(nextDimensionIndexes) + 1

    ' 1. 如果没有指定任何参数，将直接返回当前对象本身
    If indexSize = 0 Then
        Err.Raise 20010, Description:="ArrayMatrix_getSubDimensionByParams: can not get dimension, because not set parameter[nextDimensionIndexes]"
        Exit Function
    End If

    ' 2. 检查维度数量，如果已经到了 N 级，则抛出异常
    If indexSize >= fieldDimensionCount Then
        Err.Raise 20011, Description:="ArrayMatrix_getSubDimensionByParams: matrix dimension count is [" & fieldDimensionCount & "], level of excepted dimension is [" & indexSize & "], can not get next dimension, because next dimension is real data or has exceeded the maximun level" & _
                        vbCrLf & _
                        "param[nextDimensionIndexes]=" & UtilsArray.joinElemForParamsVal(nextDimensionIndexes, ",")
        Exit Function
    End If

    ' 3. 检查 nextDimensionIndexes 参数中各维度索引的是否在正确的范围内，如果超出了范围需要抛出异常
    Dim i As Integer
    For i = 0 To indexSize - 1
        If nextDimensionIndexes(i) < 0 Then
            Err.Raise 20012, Description:="ArrayMatrix_getSubDimensionByParams: nextDimensionIndexes[" & i & "]=" & nextDimensionIndexes(i) & _
                ", is less 0, can not get next dimension"
            Exit Function
        End If

        If nextDimensionIndexes(i) >= fieldSizeArray(i) Then
            Err.Raise 20013, Description:="ArrayMatrix_getSubDimensionByParams: nextDimensionIndexes[" & i & "]=" & nextDimensionIndexes(i) & _
                ", is great than max index[" & (fieldSizeArray(i) - 1) & "], can not get next dimension"
            Exit Function
        End If
    Next

    ' 4. 创建指定的维度对象
    Dim result As ArrayMatrixDimension
    Set result = New ArrayMatrixDimension
    result.Init Me, UtilsArray.clone1DArrayForParamVal(nextDimensionIndexes)

    Set getSubDimensionByParams = result
End Function

' 获取指定索引的数据
' 对于 0 - N 级的维度，假设当前维度对象所在的维度是 X，
' 参数 nextDimensionIndexes 必须传入从 X + 1 -- N 级每一集的维度索引，否则将会抛出异常
' 如果各维度的索引值超过的维度要求的范围，将会抛出异常
' @param nextDimensionIndexes 从下一级开始直到第 N 级的所有维度索引
' @return 指定位置上的数据
Public Function getData(ParamArray nextDimensionIndexes() As Variant) As Variant
    ' 1. 检查维度数量，如果没有到达 N 级，则抛出异常
    Dim indexSize As Integer
    indexSize = UBound(nextDimensionIndexes) + 1

    If indexSize <> fieldDimensionCount Then
        Err.Raise 20014, Description:="ArrayMatrix_getData: matrix dimension count is [" & fieldDimensionCount & "], level of excepted dimension is [" & indexSize & "], can not get real data." & _
                        vbCrLf & _
                        "param[nextDimensionIndexes]=" & UtilsArray.joinElemForParamsVal(nextDimensionIndexes, ",")
        Exit Function
    End If

    ' 2. 检查 indexes 参数中各维度索引的是否在正确的范围内，如果超出了范围需要抛出异常
    Dim i As Integer
    For i = 0 To indexSize - 1
        If nextDimensionIndexes(i) < 0 Then
            Err.Raise 20015, Description:="ArrayMatrix_getData: nextDimensionIndexes[" & i & "]=" & nextDimensionIndexes(i) & _
                ", is less 0, can not get next dimension"
            Exit Function
        End If

        If nextDimensionIndexes(i) >= fieldSizeArray(i) Then
            Err.Raise 20015, Description:="ArrayMatrix_getData: nextDimensionIndexes[" & i & "]=" & nextDimensionIndexes(i) & _
                ", is great than max index[" & (fieldSizeArray(i) - 1) & "], can not get next dimension"
            Exit Function
        End If
    Next

    ' 3. 获取数据
On Error GoTo catch:
    getData = UtilsArray.getArrayValueByParamsVal(cache, nextDimensionIndexes)
    Exit Function
catch:
    Set getData = UtilsArray.getArrayValueByParamsVal(cache, nextDimensionIndexes)
End Function

' 从指定索引处获取基础类型的数据
' 对于 0 - N 级的维度，假设当前维度对象所在的维度是 X，
' 参数 nextDimensionIndexes 必须传入从 X + 1 -- N 级每一集的维度索引，否则将会抛出异常
' 如果各维度的索引值超过的维度要求的范围，将会抛出异常
' @param nextDimensionIndexes 从下一级开始直到第 N 级的所有维度索引
' @return 指定位置上的数据
Public Function getPrimitiveData(ParamArray nextDimensionIndexes() As Variant) As Variant
    ' 1. 检查维度数量，如果没有到达 N 级，则抛出异常
    Dim indexSize As Integer
    indexSize = UBound(nextDimensionIndexes) + 1

    If indexSize <> fieldDimensionCount Then
        Err.Raise 20014, Description:="ArrayMatrix_getPrimitiveData: matrix dimension count is [" & fieldDimensionCount & "], level of excepted dimension is [" & indexSize & "], can not get real data." & _
                        vbCrLf & _
                        "param[nextDimensionIndexes]=" & UtilsArray.joinElemForParamsVal(nextDimensionIndexes, ",")
        Exit Function
    End If

    ' 2. 检查 indexes 参数中各维度索引的是否在正确的范围内，如果超出了范围需要抛出异常
    Dim i As Integer
    For i = 0 To indexSize - 1
        If nextDimensionIndexes(i) < 0 Then
            Err.Raise 20015, Description:="ArrayMatrix_getPrimitiveData: nextDimensionIndexes[" & i & "]=" & nextDimensionIndexes(i) & _
                ", is less 0, can not get next dimension"
            Exit Function
        End If

        If nextDimensionIndexes(i) >= fieldSizeArray(i) Then
            Err.Raise 20015, Description:="ArrayMatrix_getPrimitiveData: nextDimensionIndexes[" & i & "]=" & nextDimensionIndexes(i) & _
                ", is great than max index[" & (fieldSizeArray(i) - 1) & "], can not get next dimension"
            Exit Function
        End If
    Next

    ' 3. 获取基础类型数据
    getPrimitiveData = UtilsArray.getArrayPrimitiveValueByParamsVal(cache, nextDimensionIndexes)
End Function

' 从指定索引处获取对象类型的数据
' 对于 0 - N 级的维度，假设当前维度对象所在的维度是 X，
' 参数 nextDimensionIndexes 必须传入从 X + 1 -- N 级每一集的维度索引，否则将会抛出异常
' 如果各维度的索引值超过的维度要求的范围，将会抛出异常
' @param nextDimensionIndexes 从下一级开始直到第 N 级的所有维度索引
' @return 指定位置上的数据
Public Function getObjData(ParamArray nextDimensionIndexes() As Variant) As Variant
    ' 1. 检查维度数量，如果没有到达 N 级，则抛出异常
    Dim indexSize As Integer
    indexSize = UBound(nextDimensionIndexes) + 1

    If indexSize <> fieldDimensionCount Then
        Err.Raise 20014, Description:="ArrayMatrix_getObjData: matrix dimension count is [" & fieldDimensionCount & "], level of excepted dimension is [" & indexSize & "], can not get real data." & _
                        vbCrLf & _
                        "param[nextDimensionIndexes]=" & UtilsArray.joinElemForParamsVal(nextDimensionIndexes, ",")
        Exit Function
    End If

    ' 2. 检查 indexes 参数中各维度索引的是否在正确的范围内，如果超出了范围需要抛出异常
    Dim i As Integer
    For i = 0 To indexSize - 1
        If nextDimensionIndexes(i) < 0 Then
            Err.Raise 20015, Description:="ArrayMatrix_getObjData: nextDimensionIndexes[" & i & "]=" & nextDimensionIndexes(i) & _
                ", is less 0, can not get next dimension"
            Exit Function
        End If

        If nextDimensionIndexes(i) >= fieldSizeArray(i) Then
            Err.Raise 20015, Description:="ArrayMatrix_getObjData: nextDimensionIndexes[" & i & "]=" & nextDimensionIndexes(i) & _
                ", is great than max index[" & (fieldSizeArray(i) - 1) & "], can not get next dimension"
            Exit Function
        End If
    Next

    ' 3. 获取基础类型数据
    Set getObjData = UtilsArray.getArrayObjValueByParamsVal(cache, nextDimensionIndexes)
End Function

' 供 ArrayMatrixDimension 使用来获取数据，以避免系统对数组对象 cache 产生的值类型复制
' 仅供内部使用，外部不应该直接调用该方法
Function inner_getData(ByRef dimensionIndexesPath As Variant, ByVal nextDimensionIndexes As Variant) As Variant
On Error GoTo catch:
    inner_getData = UtilsArray.getArrayValueByIndexArrayForParamsVal(cache, dimensionIndexesPath, nextDimensionIndexes)
    Exit Function
catch:
    Set inner_getData = UtilsArray.getArrayValueByIndexArrayForParamsVal(cache, dimensionIndexesPath, nextDimensionIndexes)
End Function

' 供 ArrayMatrixDimension 使用来获取【基础类型数据】，以避免系统对数组对象 cache 产生的值类型复制
' 仅供内部使用，外部不应该直接调用该方法
Function inner_getPrimitiveData(ByRef dimensionIndexesPath As Variant, ByVal nextDimensionIndexes As Variant) As Variant
    inner_getPrimitiveData = UtilsArray.getArrayPrimitiveValueByIndexArrayForParamsVal(cache, dimensionIndexesPath, nextDimensionIndexes)
End Function

' 供 ArrayMatrixDimension 使用来获取【对象类型数据】，以避免系统对数组对象 cache 产生的值类型复制
' 仅供内部使用，外部不应该直接调用该方法
Function inner_getObjData(ByRef dimensionIndexesPath As Variant, ByVal nextDimensionIndexes As Variant) As Variant
    Set inner_getObjData = UtilsArray.getArrayObjValueByIndexArrayForParamsVal(cache, dimensionIndexesPath, nextDimensionIndexes)
End Function

' 设置指定索引的数据
' 对于 0 - N 级的维度，假设当前维度对象所在的维度是 X，
' 参数 nextDimensionIndexes 必须传入从 X + 1 -- N 级每一集的维度索引，否则将会抛出异常
' 如果各维度的索引值超过的维度要求的范围，将会抛出异常
' @param val 需要设置的数据
' @param nextDimensionIndexes 从下一级开始直到第 N 级的所有维度索引
Public Sub setData(ByRef val As Variant, ParamArray nextDimensionIndexes() As Variant)
    ' 1. 检查维度数量，如果没有到达 N 级，则抛出异常
    Dim indexSize As Integer
    indexSize = UBound(nextDimensionIndexes) + 1

    If indexSize <> fieldDimensionCount Then
        Err.Raise 20016, Description:="ArrayMatrix_setData: matrix dimension count is [" & fieldDimensionCount & "], level of excepted dimension is [" & indexSize & "], can not get real data." & _
                        vbCrLf & _
                        "param[nextDimensionIndexes]=" & UtilsArray.joinElemForParamsVal(nextDimensionIndexes, ",")
        Exit Sub
    End If

    ' 2. 检查 indexes 参数中各维度索引的是否在正确的范围内，如果超出了范围需要抛出异常
    Dim i As Integer
    For i = 0 To indexSize - 1
        If nextDimensionIndexes(i) < 0 Then
            Err.Raise 20017, Description:="ArrayMatrix_setData: nextDimensionIndexes[" & i & "]=" & nextDimensionIndexes(i) & _
                ", is less 0, can not get next dimension"
            Exit Sub
        End If

        If nextDimensionIndexes(i) >= fieldSizeArray(i) Then
            Err.Raise 20015, Description:="ArrayMatrix_setData: nextDimensionIndexes[" & i & "]=" & nextDimensionIndexes(i) & _
                ", is great than max index[" & (fieldSizeArray(i) - 1) & "], can not get next dimension"
            Exit Sub
        End If
    Next

    ' 3. 设置数据
    UtilsArray.setArrayValueByParamsVal val, cache, nextDimensionIndexes
End Sub

' 供 ArrayMatrixDimension 使用来写入数据，以避免系统对数组对象 cache 产生的值类型复制
' 仅供内部使用，外部不应该直接调用该方法
Sub inner_setData(ByRef val As Variant, ByRef dimensionIndexesPath As Variant, ByVal nextDimensionIndexes As Variant)
On Error GoTo catch:
    UtilsArray.setArrayValueByIndexArrayForParamsVal val, cache, dimensionIndexesPath, nextDimensionIndexes
    Exit Sub
catch:
    UtilsArray.setArrayValueByIndexArrayForParamsVal val, cache, dimensionIndexesPath, nextDimensionIndexes
End Sub